import java.math.BigInteger;
import java.util.Scanner;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.*;
import java.util.Arrays;

/* A visitor that walks over the tree, generating code in our machine language */
public class Compiler implements Kitsch.yyTree.Visitor {

    /**
    * Fetch the entire contents of a text file, and return it in a String.
    * This style of implementation does not throw Exceptions to the caller.
    *
    * @param aFile is a file which already exists and can be read.
    */
    static public String getContents(File aFile) throws java.io.IOException {
      //...checks on aFile are elided
      StringBuilder contents = new StringBuilder();

        //use buffering, reading one line at a time
        //FileReader always assumes default encoding is OK!
        BufferedReader input =  new BufferedReader(new FileReader(aFile));
        try {
          String line = null; //not declared within while loop
          /*
          * readLine is a bit quirky :
          * it returns the content of a line MINUS the newline.
          * it returns null only for the END of the stream.
          * it returns an empty String if two newlines appear in a row.
          */
          while (( line = input.readLine()) != null){
            contents.append(line);
            contents.append(System.getProperty("line.separator"));
          }
        }
        finally {
          input.close();
        }
      return contents.toString();
    }


    public static void main( String[] args ) {
        if ( args.length != 1 && args.length != 2 ) {
            System.err.println( "Usage: java Compiler infile [outfile]" );
            System.exit( 1 );
        }
        
        try {
            File dir = new File("builtins");
            // This filter only returns Kitsch source files
            FileFilter fileFilter = new FileFilter() {
                public boolean accept(File file) {
                    return file.isFile() && file.toString().endsWith(".k");
                }
            };
            File[] files = dir.listFiles(fileFilter);
            if(files == null) {
                throw new FileNotFoundException("builtins");
            }
            ArrayList<File> files_temp = new ArrayList<File>(Arrays.asList(files));
            File source_file = new File(args[0]);
            if(!source_file.exists()) {
                throw new FileNotFoundException(args[0]);
            }
            files_temp.add(new File(args[0]));
            files = files_temp.toArray(files);
            String source = "";
            for(File f: files) {
                source += getContents(f)+"\n"; 
            }
            
            //java.io.InputStream is = new java.io.FileInputStream( args[0] );
            java.io.InputStream is = new ByteArrayInputStream(source.getBytes("UTF-8"));
            Kitsch.yyInput scanner = new Kitsch.yyLex( is );
           
            java.lang.Object parser = scanner.getClass()
            .getEnclosingClass().newInstance();
           
            // arg[0]: if specified, toggle null storage (currently disabled)
            /*if (arg != null && arg.length > 0) {
                java.lang.reflect.Field yyAddNull = parser.getClass().getField("yyAddNull");
                yyAddNull.setBoolean(parser, yyAddNull.getBoolean(parser) ^ true);
            }*/
           
            // build tree
            java.lang.Object tree = parser.getClass()
                .getMethod("yyparse", new java.lang.Class[]{
                  Kitsch.yyInput.class, java.lang.Object[].class })
                .invoke(parser, scanner,
                  new java.lang.Object[]{ new Kitsch.yyTree() });
                 
            String outfilename;

            if(args.length == 2) {
                // Outfile was specified
                outfilename = args[1];
            } else {
                // Outfile not specified
                int name_start = args[0].lastIndexOf("/")+1; 
                //TODO: Is there a System pathseparator constant in Java?? 
                //(for windows compatibility)
                if(name_start >= 0) {
                    outfilename = args[0].substring(name_start);
                } else {
                    outfilename = args[0];
                }
                int extension_start = outfilename.lastIndexOf(".");
                if(extension_start >= 0) {
                    outfilename = outfilename.substring(0,extension_start) + ".ko";
                } else {
                    outfilename = outfilename + ".ko";
                }
            }
            
            FuncVisitor fv;
            Map<FuncVisitor.Function, Kitsch.yyTree.FuncDecl> funcsToNodes;
            Map<String, Integer> globals;
            TypeChecker checker;
            Compiler comp;
            
            try {
                //collect function prototypes
                fv = new FuncVisitor();
                funcsToNodes = (Map) fv.visit( 
                                (Kitsch.yyTree.Prgm)tree );
                //type check the tree
                checker = new TypeChecker( funcsToNodes );
                globals = (Map)checker.visit( (Kitsch.yyTree.Prgm)tree );
                //compile the tree
                comp = new Compiler(outfilename, funcsToNodes, globals );
                comp.visit( (Kitsch.yyTree.Prgm)tree );
            } catch(Exception e) {
                // If the type checker generates an exception, don't print the trace, 
                //just exit the program.
                // The type checker attempts to keep checking for more errors after it 
                //detects the first one
                // but it typically leads to a null pointer exception or something like that
                //e.printStackTrace();
                System.exit(1);
            }
                 
        } catch ( java.io.FileNotFoundException ex ) {
            System.err.println( ex.getMessage() + ": File does not exist." );
            System.exit( 1 );
        } catch ( java.io.IOException ex ) {
            System.err.println( args[1] + ": Error writing to file." );
        } catch (java.lang.InstantiationException e) {
          System.err.println("cannot create parser ["+e+"]");
          System.exit(1);
        } catch (java.lang.IllegalAccessException e) {
          System.err.println("cannot create parser ["+e+"]");
          System.exit(1);
        } catch (java.lang.NoSuchMethodException e) {
          System.err.println("cannot create parser ["+e+"]");
          System.exit(1);
        } catch (java.lang.reflect.InvocationTargetException e) {
          System.err.println("parse error ["+e+"]");
          System.exit(1);
        }
    }

    //The global scope of the program, has null as outerscope
    private Scope globalScope;
    //The current scope of the program
    private Scope currentScope;
    //A scanner for receiving input
    private Scanner input;
    //The object responsible for writing files out
    private KOWriter out;
    //Has type constants
    private OperatorRegistry registry;
    private boolean assign_lhs = false;
    //Function definitions
    private Map<FuncVisitor.Function, Kitsch.yyTree.FuncDecl> funcsToNodes;
       
    /*
     * A helper class responsible for writing instructions out to the compiled file.
     * It also keeps track of the pc (program counter).
     * 
     * The general pattern of usage for this class is to use the writeInstruction() method
     * to write a string (not checked to see if it is a valid instruction!) to the
     * compiled file.
     *
     * But KOWriter also supports a notion of "deferred" instructions.  These are
     * instructions, generally branches, where it is not yet known how far in advance to
     * branch.  In this case, one writes the branch part of the instruction with the
     * deferInstruction() method, and then uses writeInstruction() to write the subsequent
     * instructions as per usual (for example, in the body of a while loop).  Once it
     * can be determined where the deferred instruction needs to branch to, one uses
     * finishInstruction() to write the remainder of the instruction fragment.
     *
     * This whole setup is in place to avoid the need for a two-pass compiler.
     */
    public class KOWriter {
        private StringWriter out;
        private PrintWriter reallyOut;
        //the program counter is public for easy lookup 
        //please be polite and do not alter it!
        public int pc;
        
        /* helper class to hold instructions that may be deferred */
        public class Pair {
            public String instr;
            public boolean deferred;
            public Pair( String instr, boolean def ) {
                this.instr = instr;
                deferred = def;
            }
        }
        
        //when an instruction has been deferred, this list holds that instruction
        //plus any other instructions written afterwards, that need to be buffered
        //here rather than written out.
        private List<Pair> deferredInstrs;
        
        /*
         * Constructs a new KOWriter using the specified outfile.  Throws
         * an IOException if there is some error opening the outfile.
         */
        public KOWriter( String outfileName ) throws java.io.IOException {
            reallyOut = new PrintWriter( new java.io.FileWriter( outfileName ) );
            out = new StringWriter();
            pc = 0;
            deferredInstrs = new ArrayList<Pair>();
        }
        
        /*
         * Write an instruction to the outfile.
         */
        public void writeInstruction( String instr ) {
            if ( deferredInstrs.isEmpty() ) {
                out.write( instr + "\n" );
            } else {
                deferredInstrs.add( new Pair( instr, false ) );
            }
            pc++;
        }
        
        /*
         * Defer an instruction fragment, finishing it later.
         */
        public void deferInstruction( String instrFrag ) {
            deferredInstrs.add( new Pair( instrFrag, true ) );
            pc++;
        }
        
        /*
         * Finish the *most recent* deferred instruction (important!).
         * This supports the notion of multiple deferred instructions, but only the
         * most recent can be finished.
         */
        public void finishInstruction( String instrFrag ) {
            java.util.ListIterator<Pair> iter = 
                deferredInstrs.listIterator( deferredInstrs.size() );
            Pair current = null;
            //find the most recent deferred instruction
            while ( iter.hasPrevious() ) {
                current = iter.previous();
                if ( current.deferred ) break;
            }
            //finish the deferred instruction
            current.instr += instrFrag;
            current.deferred = false;
            if ( current == deferredInstrs.get( 0 ) ) {
                //all deferred instructions are taken care of, write them out
                out.write( current.instr + "\n" );
                iter.remove();
                //write out the subsequent instructions and remove them
                while ( iter.hasNext() ) {
                    out.write( iter.next().instr + "\n" );
                    iter.remove();
                }
            }
        }
        
        //cleanup
        public void close() {
            //actually write everything out
            String[] instrs = out.toString().split("\n");
            for ( String instr : instrs ) {
                if ( instr.matches( "JMP [a-zA-Z][a-zA-Z0-9]*" ) ) {
                    String fname = instr.substring( instr.indexOf( " " ) + 1 );
                    FuncVisitor.Function f = lookupFunc( fname );
                    instr = "JMP #" + f.startLoc;
                }
                reallyOut.println( instr );
            }
            reallyOut.close();
        }
    }
    
    /* simple struct to represent entries of the symbol table */
    public class SymbolTableEntry {
        public String name;
        public int memoryLoc;
        public int type;
       
        public SymbolTableEntry( String name, int type ) {
            this.name = name;
            this.type = type;
        }
    }
   
    /*
     * A helper class to manage memory.  Used to malloc and free memory locations
     * for variables.  Memory locations are treated as non-negative positive integers
     * that have, for all practical purposes, infinite storage space.
     */
    public class Memory {
        //the list of currently used memory.
        private List<Integer> usedMemory;
        //the list of memory that has been freed
        private List<Integer> freedMemory;
       
        /* Construct a new Memory object */
        public Memory() {
            usedMemory = new ArrayList<Integer>();
            freedMemory = new ArrayList<Integer>();
        }
        
        /*
         * Allocate a memory location for the given SymbolTableEntry.  This modifies
         * the entry's memoryLoc attribute, and returns it for convienence.
         */
        public int malloc( SymbolTableEntry entry ) {
            int freeMem = 0;
            //is this the first thing allocated?
            if ( freedMemory.isEmpty() && usedMemory.isEmpty() ) {
                freeMem = 0;
            } else if ( freedMemory.isEmpty() ) {
                //used next unallocated slice of memory
                freeMem = usedMemory.get( usedMemory.size() - 1 ) + 1;
            } else {
                //grab a freed memory location
                freeMem = freedMemory.get( 0 );
                freedMemory.remove( 0 );
            }
            usedMemory.add( freeMem );
            entry.memoryLoc = freeMem;
            return entry.memoryLoc;
        }
        
        /*
         * Free the memory used by this SymbolTableEntry.  That memory will be reused
         * when the time comes.
         */
        public void free( SymbolTableEntry entry ) {
            usedMemory.remove( (Integer)entry.memoryLoc );
            freedMemory.add( entry.memoryLoc );
            entry.memoryLoc = -1; //unallocated, shouldn't be used  
        }

        /*
         * Figure out where the next availabe memory location is.
         */
        public int nextLoc() {
            return usedMemory.size();
        }
    }

    /*
     * A class to handle scoping.  Each Scope keeps track of it's part of the symbol
     * table, and knows of its parent scope to tie everything together.
     */
    public class Scope {
        //the scope enclosing this one
        private Scope outerscope;
        //the symbols available within this scope
        private List<SymbolTableEntry> symbols;
        //used to create temporary variable names
        private int tempCounter;
        //used to allocate/free memory
        public Memory memory;


        /*
         * Creates a new scope enclosed by outerscope, which can be null.
         */
        public Scope( Scope outerscope ) {
            this.outerscope = outerscope;
            symbols = new ArrayList<SymbolTableEntry>();
            tempCounter = 0;
            memory = new Memory();
        }

        /*
         * Destructs this scope object by deallocating all of its symbols.
         * Call *required* when the scope goes out of scope!
         */
        public void destruct() {
            for ( SymbolTableEntry symbol : symbols ) {
                memory.free( symbol );
            }
        }

        /*
         * Adds an identifier to the current scope and returns its SymbolTableEntry.
         */
        public SymbolTableEntry addToScope( String ident, int type ) {
            if ( get( ident ) != null ) {
                //behaviour for now is to scream and die at any conflicts
                System.err.println( "Fatal: ident " + ident +
                " is already in scope." );
                System.exit( 1 );
            }
            SymbolTableEntry symbol = new SymbolTableEntry( ident, type );
            symbols.add( symbol );
            memory.malloc( symbol );
            return symbol;
        }
       
        /*
         * Creates a temporary variable in the current scope.
         */
        public SymbolTableEntry createTemp( int type ) {
            SymbolTableEntry temp = new SymbolTableEntry( "temp" + tempCounter++, type );
            symbols.add( temp );
            memory.malloc( temp );
            return temp;
        }

        /*
         * Looks for the value of the identifier in this scope, and escalates
         * to the enclosing scope if it can't find it here.  If the return is
         * null, that means that there is no such identifier.
         */
        public SymbolTableEntry get( String ident ) {
            SymbolTableEntry entry = lookup( ident );
            if ( entry == null && outerscope != null) {
                entry = outerscope.get( ident );
            }
            return entry;
        }
       
        private SymbolTableEntry lookup( String ident ) {
            SymbolTableEntry find = null;
            for ( SymbolTableEntry entry : symbols ) {
                if ( entry.name.equals( ident ) ) {
                    find = entry;
                }
            }
            return find;
        }
    }
    
    /* Construct a new compiler object that writes to outfileName */
    public Compiler( String outfileName, Map<FuncVisitor.Function,
                     Kitsch.yyTree.FuncDecl> funcsToNodes,
                     Map<String, Integer> globals ) throws java.io.IOException {
        globalScope = new Scope( null );
        currentScope = globalScope;
        input = new Scanner( System.in );
        out = new KOWriter( outfileName );
        this.funcsToNodes = funcsToNodes;
        //load globals into the global scope
        SymbolTableEntry global;
        for ( String ident : globals.keySet() ) {
            global = globalScope.addToScope( ident, globals.get( ident ) );
            out.writeInstruction( "LOAD #0" );
            out.writeInstruction( "STORE &" + global.memoryLoc );
        }
        //write the initial stack pointer
        out.writeInstruction( "LOAD #" + globals.size() );
        out.writeInstruction( "STORESP" );
    }
    
    /* Helper function for calling built-ins
     * args is a list of strings that represent that arguments that needed to be loaded on to the stack for the function
     * The return value will be on the top of the stack when finished
     */
    private void callBuiltin(String funcname, List<String> args) {
        FuncVisitor.Function func = lookupFunc( funcname );        
        
        // Load the arguments
        for(int i=args.size()-1; i >= 0; i--) {
            out.writeInstruction("LOAD "+args.get(i));
        }
        
        // Load the stack pointer so we can save it later
        out.writeInstruction("LOADSP");
        
        // Make a new memory scope (stack frame), compute new stack pointer 
        //location, store it
        Scope s = new Scope(globalScope);
        out.writeInstruction("LOADSP");
        out.writeInstruction("LOAD #" + currentScope.memory.nextLoc() );
        out.writeInstruction("ADD" );
        out.writeInstruction("STORESP");
                 
        // push old stack pointer
        SymbolTableEntry sp = s.addToScope("0",OperatorRegistry.INT);
        out.writeInstruction("STORE "+sp.memoryLoc);

        // push return address
        SymbolTableEntry pc = s.addToScope("1",OperatorRegistry.INT);
        out.deferInstruction("LOAD ");
        out.writeInstruction("STORE "+pc.memoryLoc);   
         
        //add the parameters
        for(int i=0; i<args.size();i++) {
            FuncVisitor.Parameter p = func.params.get( i );
            SymbolTableEntry ap = s.addToScope( p.name, p.type );
            out.writeInstruction( "STORE " + ap.memoryLoc );
        }

        // Jump to function
        out.finishInstruction("#"+(out.pc+1));
        out.writeInstruction("JMP #"+func.startLoc);
         
        // Restore old stack pointer
        out.writeInstruction("LOAD "+sp.memoryLoc);
        out.writeInstruction("STORESP");
    }

    private FuncVisitor.Function lookupFunc( String fname ) {
        for ( FuncVisitor.Function f : funcsToNodes.keySet() ) {
            if ( f.name.equals( fname ) ) {
                return f;
            }
        }
        System.err.println( "Error: Function " + fname + " does not exist." );
        System.exit( -1 );
        return null;
    }

    /**
     * To compile a program node, we compile each subnode in the program.
     */
    public java.lang.Object visit (Kitsch.yyTree.Prgm node) {
        Kitsch.yyTree.top_stmt elem;
        currentScope = new Scope( globalScope );

        out.deferInstruction( "JMP " );
        // Visit all the function declarations
        for ( int x = 0; x < node.size(); x++ ) {
            elem = (Kitsch.yyTree.top_stmt)node.get( x );
            if ( elem != null && (elem.get(0) instanceof Kitsch.yyTree.FuncDecl)) {
                elem.visit( this );
            }
        }
        out.finishInstruction( "#" + (out.pc) );
        
        //visit everything else
        for ( int x = 0; x < node.size(); x++ ) {
            elem = (Kitsch.yyTree.top_stmt)node.get( x );
            if ( elem != null && !(elem.get(0) instanceof Kitsch.yyTree.FuncDecl ) ) {
                elem.visit( this );
            }
        }
        
        out.writeInstruction( "NOOP" );
        out.close(); //cleanup file
        return null;
    }
    
    public java.lang.Object visit ( Kitsch.yyTree.top_stmt node) {
        Kitsch.yyTree.Visit elem;
        for ( int x = 0; x < node.size(); x++ ) {
            elem = (Kitsch.yyTree.Visit)node.get( x );
            if(elem != null) {
                elem.visit( this );
            }
        }
        return null;
    }

    /**
     * To compile a statement node, we compile each subnode.
     * There should really only be one, but pj2 wants to put it in a list anyway.
     */
    public java.lang.Object visit (Kitsch.yyTree.Stmt node) {
        Kitsch.yyTree.Visit elem;
        for ( int x = 0; x < node.size(); x++ ) {
            elem = (Kitsch.yyTree.Visit)node.get( x );
            elem.visit( this );
        }
        return null;
    }

    /**
     * An if statement has a single if block, an arbitrary number of elif blocks,
     * and possibly an else block.
     */
    public java.lang.Object visit (Kitsch.yyTree.IfStmt node) {
        //visit all the if/elif/else blocks
        Kitsch.yyTree.Visit elem;
        List<Integer> pcs = new ArrayList<Integer>();
        for ( int x = 0; x < node.size(); x++ ) {
            //Scope oldScope = currentScope;
            //currentScope = new Scope( oldScope );
            elem = (Kitsch.yyTree.Visit)node.get( x );
            if ( elem != null ) {
                pcs.add( (Integer)elem.visit( this ) );
            }
            //currentScope.destruct();
            //currentScope = oldScope;
        }
        //finish all of the BRA instructions by walking backwards over the pc list
        for ( int x = pcs.size() - 1; x >= 0; x-- ) {
            out.finishInstruction( "#" + (out.pc - pcs.get(x) + 1) );
        }
        return null;
    }
   
    public java.lang.Object visit (Kitsch.yyTree.IfBlock node) {
        //code to perform the comparison
        Kitsch.yyTree.Expr expr = (Kitsch.yyTree.Expr)node.get(0);
        //negate - branch if comp is false
        String branch = (String)expr.visit( this );
        if ( negate( branch ).equals( "error" ) ) {
            //boolean variable or literal
            out.writeInstruction( "LOAD " + branch );
            out.writeInstruction( "LOAD #1" );
            out.deferInstruction( "BNE " );
        } else {
            out.deferInstruction( negate( branch ) );
        }
        int condPC = out.pc;
        //write out the body
        for ( int x = 1; x < node.size(); x++ ) {
            ((Kitsch.yyTree.Visit)node.get(x)).visit( this );
        }
        //finish the branch instruction
        out.finishInstruction( "#" + (out.pc - condPC + 2 ) ); //the 2 is for the next BRA
        //write out the BRA to the entire if statement
        out.deferInstruction( "BRA " );
        return out.pc; //used to finish the BRA
    }
    
    public java.lang.Object visit (Kitsch.yyTree.ElifBlock node) {
        //code to perform the comparison
        Kitsch.yyTree.Expr expr = (Kitsch.yyTree.Expr)node.get(0);
        //negate - branch if comp is false
        String branch = (String)expr.visit( this );
        if ( negate( branch ).equals( "error" ) ) {
            //boolean variable or literal
            out.writeInstruction( "LOAD " + branch );
            out.writeInstruction( "LOAD #1" );
            out.deferInstruction( "BNE " );
        } else {
            out.deferInstruction( negate( branch ) );
        }
        int condPC = out.pc;
        //write out the body
        for ( int x = 1; x < node.size(); x++ ) {
            ((Kitsch.yyTree.Visit)node.get(x)).visit( this );
        }
        //finish the branch instruction
        out.finishInstruction( "#" + (out.pc - condPC + 2 ) ); //the 2 is for the next BRA
        //write out the BRA to the entire if statement
        out.deferInstruction( "BRA " );
        return out.pc; //used to finish the BRA
    }
    public java.lang.Object visit (Kitsch.yyTree.ElseBlock node) {
        //write out the body
        for ( int x = 0; x < node.size(); x++ ) {
            ((Kitsch.yyTree.Visit)node.get(x)).visit( this );
        }
        //write out the BRA to the entire if statement
        out.deferInstruction( "BRA " );
        return out.pc; //used to finish the BRA
    }
    
    private void handleBoolOp( String branch ) {
        out.writeInstruction( negate( branch ) + "#3" );
        out.writeInstruction( "LOAD #1" );
        out.writeInstruction( "BRA #2" );
        out.writeInstruction( "LOAD #0" );
    }
    
    /*
     * Helper function to negate a branching instruction - eg, the negation of BLT
     * (branch if less than) is BGE (branch if greater than or equal to).
     */
    private String negate( String comp ) {
        if ( comp.equals( "BEQ " ) ) {
            return "BNE ";
        } else if ( comp.equals( "BNE " ) ) {
            return "BEQ ";
        } else if ( comp.equals( "BLT " ) ) {
            return "BGE ";
        } else if ( comp.equals( "BLE " ) ) {
            return "BGT ";
        } else if ( comp.equals( "BGT " ) ) {
            return "BLE ";
        } else if ( comp.equals( "BGE " ) ) {
            return "BLT ";
        } else if ( comp.equals( "BRA " ) ) {
            return "BRA ";
        } else {
            return "error";
        }
    }
   
    /**
     * A while is made up of two elements:
     * 0 - the comparison
     * 1 - the body of the while
     */
    public java.lang.Object visit (Kitsch.yyTree.WhileStmt node) {
        Kitsch.yyTree.Expr expr = (Kitsch.yyTree.Expr)node.get(0);
        //code to test the comparison
        int topOfLoopPC = out.pc;
        String branch = (String)expr.visit( this );
        if ( negate( branch ).equals( "error" ) ) {
            //boolean variable or literal
            out.writeInstruction( "LOAD " + branch );
            out.writeInstruction( "LOAD #1" );
            out.deferInstruction( "BNE " );
        } else {
            out.deferInstruction( negate( branch ) );
        }
        int branchPC = out.pc;
        //write out instructions for the body of the loop
        Kitsch.yyTree.Visit elem;
        //Scope oldScope = currentScope;
        //currentScope = new Scope( oldScope );
        for ( int x = 1; x < node.size(); x++ ) {
            elem = (Kitsch.yyTree.Visit)node.get( x );
            if ( elem != null ) {
                elem.visit( this );
            }
        }
        //currentScope.destruct(); //destruct the created scope
        //currentScope = oldScope;
        //always branch up to the comparison stuff
        out.writeInstruction( "BRA #" + (topOfLoopPC - out.pc) );
        //finish the branch instruction - branch to 1 past the current instr (out of loop)
        out.finishInstruction( "#" + (out.pc - branchPC + 1) );
        return null; //nothing really to return
    }

    public java.lang.Object visit( Kitsch.yyTree.GlbDecl node ) {
        //no need to do anything, globals are already added to the global scope
        return null;
    }

    /**
     * Assignment has two elements, the identifier and the value.
     */
    public java.lang.Object visit (Kitsch.yyTree.Assign node) {
        boolean array_assignment = node.get(1) != null;
        
        String ident = (String)((Kitsch.yyTree.IdCls)node.get(0)).get(0);
        
        if ( currentScope.get( ident ) == null ) {
            //variable hasn't been declared yet, make it
            if(!array_assignment) {
                currentScope.addToScope( ident, node.getType() );
            } else {
                // The array doesn't exist, we need to create it
                // This should probably be done with an assembly function... 
                // rather than a ton of inline code 
                // but we currently don't have any way of doing that
                // I don't see how we can write it in kitsch, b/c we don't have 
                // low enough memory access / ability to manipulate
                // pointers in the language. No way to setup the array lengths and stuff
                SymbolTableEntry new_var = currentScope.addToScope( ident,
                    ((Kitsch.yyTree.Visit)node.get(0)).getType());
                String indx = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
                if(indx != null) {
                    out.writeInstruction("LOAD "+indx);
                } else {
                    // Value of offset is on the stack already
                }
                //pull the index off the stack into a temp
                SymbolTableEntry index = currentScope.createTemp( OperatorRegistry.INT );
                out.writeInstruction( "STORE " + index.memoryLoc );
                out.writeInstruction( "LOAD " + index.memoryLoc );
                // The value of the offset is now loaded on the stack, 
                // Adjust for fact that index was 0-based...
                out.writeInstruction("LOAD #1");
                out.writeInstruction("ADD");
                // Maximum size is twice this...
                out.writeInstruction( "LOAD #2" );
                out.writeInstruction( "MUL" );
                // Reserve an extra 2 spaces for the size/allocated size
                out.writeInstruction( "LOAD #1" );
                out.writeInstruction( "ADD" );
                // Create temporary for the parameter to malloc
                SymbolTableEntry size = currentScope.createTemp(OperatorRegistry.INT);
                out.writeInstruction("STORE "+size.memoryLoc);
                List<String> args = new ArrayList<String>();
                args.add(""+size.memoryLoc);
                // Allocate memory for the array
                callBuiltin("malloc",args);
                // The pointer is now on the stack, save it to another temporary
                SymbolTableEntry pointer = currentScope.createTemp(OperatorRegistry.INT);
                out.writeInstruction("STORE "+pointer.memoryLoc);
                // Make a copy of the original pointer for later use
                out.writeInstruction("LOAD "+pointer.memoryLoc);
                // Load the index of the array again
                out.writeInstruction("LOAD "+index.memoryLoc);
                // Compensate for index being 0-based
                out.writeInstruction("LOAD #1");
                out.writeInstruction("ADD");
                // The size is now on the stack, save it to the pointer
                out.writeInstruction("STORE @"+pointer.memoryLoc);
                // Make the max size twice the initial size
                out.writeInstruction("LOAD @"+pointer.memoryLoc);
                out.writeInstruction( "LOAD #2" );
                out.writeInstruction( "MUL" );
                // Save it to the next address in the array
                out.writeInstruction("LOAD "+pointer.memoryLoc);
                out.writeInstruction("LOAD #-1");
                out.writeInstruction("ADD");
                out.writeInstruction("STORE "+pointer.memoryLoc);
                out.writeInstruction("STORE @"+pointer.memoryLoc);
                
                // Finally, store the array pointer with the identifier
                out.writeInstruction("STORE "+new_var.memoryLoc);
                
                // Initialize the elements of the array
                args = new ArrayList<String>();
                args.add(""+new_var.memoryLoc);
                args.add( "#0" );
                args.add(""+index.memoryLoc);
                if(node.getType() == OperatorRegistry.INT) {
                    callBuiltin("initIntArray",args);
                } else if(node.getType() == OperatorRegistry.STRING) {
                    callBuiltin("initStringArray",args);                    
                } else if(node.getType() == OperatorRegistry.BOOL) {
                    callBuiltin("initBoolArray",args);
                } else {
                    System.err.println("Unknown type for array elements. Cannot initialize.");
                    System.err.println( "Type " + OperatorRegistry.typeToString
                        ( node.getType() ) );
                    System.exit(1);
                }
            }
        }
        assign_lhs = true;
        String lhs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        assign_lhs = false;
        String rhs = (String)((Kitsch.yyTree.Visit)node.get(2)).visit( this );
        if ( node.getType() == OperatorRegistry.BOOL ) {
            //logical operation was performed, need to work with the stack
            handleBoolOp( rhs ); 
        } else if ( rhs != null ) {
            //rhs isn't on the stack, push it on
            out.writeInstruction( "LOAD " + rhs );
        }
        //rhs is on the top of the stack, store it into symbol's memory location
        if(!array_assignment) {
            out.writeInstruction( "STORE " + lhs );
        } else {
            // Evaluate the middle to get the memory offset
            String indx = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
            if(indx != null) {
                out.writeInstruction("LOAD "+indx);
            }
            //pull the index off the stack into a temp
            SymbolTableEntry index = currentScope.createTemp( OperatorRegistry.INT );
            out.writeInstruction( "STORE " + index.memoryLoc );
            
            // Make sure value of the pointer is on the stack
            if(lhs != null) {
                out.writeInstruction("LOAD "+lhs);
            }
            //pull this off as a temp, too
            SymbolTableEntry pointer = currentScope.createTemp( OperatorRegistry.INT );
            out.writeInstruction( "STORE " + pointer.memoryLoc );
            
            //check against the two sizes and see if the array needs to change
            
            //first, check against the max allocated size
            out.writeInstruction( "LOAD " + index.memoryLoc ); 
            out.writeInstruction( "LOAD #1" );
            out.writeInstruction( "ADD" );
            //the index+1 is now on the stack, compare against the max size
            out.writeInstruction( "LOAD #1" );
            out.writeInstruction( "LOAD " + pointer.memoryLoc ); //pointer
            out.writeInstruction( "SUB" ); //pointer to max size is now on the stack
            out.writeInstruction( "STORE " + pointer.memoryLoc );
            out.writeInstruction( "LOAD @" + pointer.memoryLoc );
            //make pointer an array pointer again, it's one off
            out.writeInstruction( "LOAD " + pointer.memoryLoc );
            out.writeInstruction( "LOAD #1" );
            out.writeInstruction( "ADD" );
            out.writeInstruction( "STORE " + pointer.memoryLoc );
            //max size it at the top of the stack, index is at the next level
            //branch past relocation code if max_size <= index
            int realloc_pc = out.pc;
            out.deferInstruction( "BGT #" ); 
            //do the reallocation
            //take the index and double it, that'll be the new max size
            out.writeInstruction( "LOAD " + index.memoryLoc );
            out.writeInstruction( "LOAD #1" );
            out.writeInstruction( "ADD" ); //index+1 = size is on top of the stack
            out.writeInstruction( "LOAD #2" );
            out.writeInstruction( "MUL" ); //twice the size is on top of the stack
            out.writeInstruction( "LOAD #2" );
            out.writeInstruction( "ADD" ); //final size is on top of the stack
            SymbolTableEntry newSize = currentScope.createTemp( OperatorRegistry.INT );
            out.writeInstruction( "STORE " + newSize.memoryLoc );
            //allocate memory for the new array
            List<String> args = new ArrayList<String>();
            args.add( "" + newSize.memoryLoc );
            callBuiltin( "malloc", args );
            SymbolTableEntry newPointer = currentScope.createTemp( OperatorRegistry.INT );
            out.writeInstruction( "STORE " + newPointer.memoryLoc );
            //set up the current size and max size of the new pointer
            out.writeInstruction( "LOAD #1" );
            out.writeInstruction( "LOAD " + newPointer.memoryLoc );
            out.writeInstruction( "SUB" );
            out.writeInstruction( "STORE " + newPointer.memoryLoc ); //pointer to max size
            out.writeInstruction( "LOAD #2" );
            out.writeInstruction( "LOAD " + newSize.memoryLoc );
            out.writeInstruction( "SUB" ); //stop counting the two size elements
            out.writeInstruction( "STORE " + newSize.memoryLoc );
            out.writeInstruction( "LOAD " + newSize.memoryLoc );
            out.writeInstruction( "STORE @" + newPointer.memoryLoc ); //store the max size
            out.writeInstruction( "LOAD " + newPointer.memoryLoc );
            out.writeInstruction( "LOAD #1" );
            out.writeInstruction( "ADD" ); //reset newPointer to the start of the array
            out.writeInstruction( "STORE " + newPointer.memoryLoc );
            //set the current size (index+1)
            out.writeInstruction( "LOAD " + index.memoryLoc );
            out.writeInstruction( "LOAD #1" );
            out.writeInstruction( "ADD" );
            out.writeInstruction( "STORE @" + newPointer.memoryLoc ); //finished setting up
            //call the appropriate copyArray function
            args = new ArrayList<String>();
            args.add( "" + pointer.memoryLoc ); //old array
            args.add( "" + newPointer.memoryLoc ); //new array
            args.add( "@" + pointer.memoryLoc ); //old size
            args.add( "@" + newPointer.memoryLoc ); //new size
            if(node.getType() == OperatorRegistry.INT) {
                callBuiltin("copyIntArray",args);
            } else if(node.getType() == OperatorRegistry.STRING) {
                callBuiltin("copyStringArray",args);                    
            } else if(node.getType() == OperatorRegistry.BOOL) {
                callBuiltin("copyBoolArray",args);
            } else {
                System.err.println("Unknown type for array elements. Cannot initialize.");
                System.err.println( "Type " + OperatorRegistry.typeToString( 
                    node.getType() ) );
                System.exit(1);
            }
            //change pointer to point to newPointer
            out.writeInstruction( "LOAD " + newPointer.memoryLoc );
            out.writeInstruction( "STORE " + pointer.memoryLoc );
            //change the *real* pointer to point to newPointer
            out.writeInstruction( "LOAD " + newPointer.memoryLoc );
            out.writeInstruction( "STORE " + currentScope.lookup( ident ).memoryLoc );
            //branch here
            out.finishInstruction( "" + (out.pc - realloc_pc ) );
            
            //now we need to handle the case that we have enough space allocated, but
            //the index is bigger than the currently initialized size of the array
            out.writeInstruction( "LOAD " + index.memoryLoc ); 
            out.writeInstruction( "LOAD #1" );
            out.writeInstruction( "ADD" ); //index+1 is on top of the stack
            out.writeInstruction( "LOAD @" + pointer.memoryLoc );
            //current size is at the top of the stack, suggested size is next
            //branch past size changing code if current_size <= index+1
            realloc_pc = out.pc;
            out.deferInstruction( "BGT #" );
            //the size is already there, we just need to initialize it
            args = new ArrayList<String>();
            args.add( "" + pointer.memoryLoc ); //a
            //reuse newSize, but make the name slightly less confusing since it's storing
            //the old size of the array (for the start index)
            SymbolTableEntry oldSize = newSize; 
            out.writeInstruction( "LOAD @" + pointer.memoryLoc );
            out.writeInstruction( "STORE " + oldSize.memoryLoc );
            args.add( "" + oldSize.memoryLoc ); //start
            //update the size of the array to be the index + 1
            out.writeInstruction( "LOAD " + index.memoryLoc );
            out.writeInstruction( "LOAD #1" );
            out.writeInstruction( "ADD" );
            out.writeInstruction( "STORE @" + pointer.memoryLoc );
            args.add( "@" + pointer.memoryLoc ); //end
            //call the appropriate initArray function
            if(node.getType() == OperatorRegistry.INT) {
                callBuiltin("initIntArray",args);
            } else if(node.getType() == OperatorRegistry.STRING) {
                callBuiltin("initStringArray",args);                    
            } else if(node.getType() == OperatorRegistry.BOOL) {
                callBuiltin("initBoolArray",args);
            } else {
                System.err.println("Unknown type for array elements. Cannot initialize.");
                System.err.println( "Type " + OperatorRegistry.typeToString( 
                    node.getType() ) );
                System.exit(1);
            }
            //branch here
            out.finishInstruction( "" + (out.pc - realloc_pc ) );
            
            out.writeInstruction( "LOAD " + index.memoryLoc );
            out.writeInstruction( "LOAD " + pointer.memoryLoc );
            out.writeInstruction("SUB");
            // Subtract another 2, to get past the length/allocation size that is 
            // in the array storage
            out.writeInstruction("LOAD #-2");
            out.writeInstruction("ADD");
            
            // Create a temporary location for computing effective address
            SymbolTableEntry toWrite = currentScope.createTemp(OperatorRegistry.INT);
            out.writeInstruction("STORE "+toWrite.memoryLoc);
            
            // Now that we've computed the effective address, store the element 
            // via indirect addressing
            out.writeInstruction("STORE @"+toWrite.memoryLoc);
        }
        return null; //nothing in particular to return
    }

    public java.lang.Object visit( Kitsch.yyTree.FuncDecl node ) {
        FuncVisitor.Function f = lookupFunc( (String)node.get(0) );
        f.startLoc = out.pc;
        Scope oldScope = currentScope;
        currentScope = new Scope( globalScope );
        //add the sp and pc as the 1st and 2nd locations of the scope
        currentScope.addToScope( "0", OperatorRegistry.INT );
        currentScope.addToScope( "1", OperatorRegistry.INT );
        //visit the parameters
        ((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        //visit the body
        Kitsch.yyTree.Visit elem;
        for ( int x = 2; x < node.size()-1; x++ ) {
            elem = (Kitsch.yyTree.Visit)node.get( x );
            if ( elem != null ) {
                elem.visit( this );
            }
        }
        //visit the return
        Kitsch.yyTree.Visit returnNode = (Kitsch.yyTree.Visit)node.get( node.size() - 1 );
        if ( returnNode == null ) {
            //out.writeInstruction( "LOAD #0" );
        } else {
            String result = (String)returnNode.visit( this );
            if ( returnNode.getType() == OperatorRegistry.BOOL ) {
                handleBoolOp( result );
            } else if ( result != null ) {
                out.writeInstruction( "LOAD " + result );
                out.writeInstruction( (String)((Kitsch.yyTree.Visit)((Kitsch.yyTree.RetCls)
                    returnNode).get(0)).visit(this));
            }
        }
        SymbolTableEntry pc = currentScope.get( "1" ); //get the pc to jump to
        out.writeInstruction( "JMP " + pc.memoryLoc );
        //restore old scope
        currentScope.destruct();
        currentScope = oldScope;
        return null;
    }

    public java.lang.Object visit( Kitsch.yyTree.Params node ) {
        Kitsch.yyTree.Visit elem;
        int paramIndex = 0;
        for ( int x = 0; x < node.size(); x++ ) {
            elem = (Kitsch.yyTree.Visit)node.get( x );
            if ( elem != null ) {
                if ( elem instanceof Kitsch.yyTree.IdCls ) {
                    String name = (String)((Kitsch.yyTree.IdCls)elem).get(0);
                    int type = ((Kitsch.yyTree.IdCls)elem).getType();
                    currentScope.addToScope( name, type );
                } else {
                    String name = (String)((Kitsch.yyTree.IdCls)
                        ((Kitsch.yyTree.DefPar)elem).get(0)).get(0);
                    int type = ((Kitsch.yyTree.DefPar)elem).getType();
                    currentScope.addToScope( name, type );
                }
            }
        }
        return null;
    }
    
    public java.lang.Object visit( Kitsch.yyTree.DefPar node ) {
        //never visited
        return null;
    }
    
    public java.lang.Object visit( Kitsch.yyTree.RetCls node ) {
        // Visit the expression, the type conversion is handled by the FuncDecl node
        String result = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        return result;
    }

    public java.lang.Object visit (Kitsch.yyTree.Print node) {
        String memLoc = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        if ( node.getType() == OperatorRegistry.BOOL ) {
            handleBoolOp( memLoc );
        } else if ( memLoc != null ) {
            //in memory/constant rather than on the stack
            out.writeInstruction( "LOAD " + memLoc );
        } 
        
        if ( node.getType() == OperatorRegistry.INT ) {
            out.writeInstruction( "TOSTR" );
        } else if ( node.getType() == OperatorRegistry.BOOL ) {
            out.writeInstruction( "LOAD #1" );
            out.writeInstruction( "BEQ #3" );
            out.writeInstruction( "LOAD \"False\"" );
            out.writeInstruction( "BRA #2" );
            out.writeInstruction( "LOAD \"True\"" );
        } else if ( node.getType() == OperatorRegistry.INT_ARRAY ) {
            List<String> args = new ArrayList<String>();
            SymbolTableEntry temp = currentScope.createTemp(OperatorRegistry.INT);
            out.writeInstruction("STORE "+temp.memoryLoc);
            args.add(""+temp.memoryLoc); // array pointer
            args.add("#1"); // really_print = True
            callBuiltin("printIntArray",args);
            return null;
        } else if ( node.getType() == OperatorRegistry.STR_ARRAY ) {
            List<String> args = new ArrayList<String>();
            SymbolTableEntry temp = currentScope.createTemp(OperatorRegistry.INT);
            out.writeInstruction("STORE "+temp.memoryLoc);
            args.add(""+temp.memoryLoc); // array pointer
            args.add("#1"); // really_print = True
            callBuiltin("printStrArray",args);
            return null;
        } else if ( node.getType() == OperatorRegistry.BOOL_ARRAY ) {
            List<String> args = new ArrayList<String>();
            SymbolTableEntry temp = currentScope.createTemp(OperatorRegistry.INT);
            out.writeInstruction("STORE "+temp.memoryLoc);
            args.add(""+temp.memoryLoc); // array pointer
            args.add("#1"); // really_print = True
            callBuiltin("printBoolArray",args);
            return null;
        }
        out.writeInstruction( "PNT" );
        return null;
    }

    public java.lang.Object visit (Kitsch.yyTree.Read node) {
        String ident = (String)((Kitsch.yyTree.IdCls)node.get(0)).get(0);
        if ( currentScope.get( ident ) == null ) {
            currentScope.addToScope( ident, node.getType() );
        }
        assign_lhs = true;
        String memLoc = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        assign_lhs = false;
        if ( node.getType() == OperatorRegistry.BOOL ) {
            out.writeInstruction( "READBOOL" );
        } else if ( node.getType() == OperatorRegistry.INT ) {
            out.writeInstruction( "READINT" );
        } else {
            out.writeInstruction( "READSTR" );
        }
        out.writeInstruction( "STORE " + memLoc );
        return null;
    }
     
    private Object doBinaryComp( String lhs, String rhs, int type ) {
         //LHS is already on the stack, before RHS
         if ( lhs == null && rhs != null ) {
            SymbolTableEntry temp = currentScope.createTemp( type );
            lhs = "" + temp.memoryLoc;
            out.writeInstruction( "STORE " + lhs );
         }
         if ( rhs != null ) {
            //load RHS onto stack
            out.writeInstruction( "LOAD " + rhs );
         }
         if ( lhs != null ) {
            //load LHS onto stack
            out.writeInstruction( "LOAD " + lhs );
         }
         return null;
    }


    /**
     * These functions push the LHS and RHS onto the stack (note that the RHS gets
     * pushed first), and, as a convienence, return the correct branching operation
     * to use, though they do *not* write this instruction out.
     */
     
    public java.lang.Object visit (Kitsch.yyTree.Lt node) {
        String rhb = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        String lhb = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        doBinaryComp( lhb, rhb, node.getType() );
        return "BLT ";
    }
    public java.lang.Object visit (Kitsch.yyTree.Lte node) {
        String rhb = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        String lhb = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        doBinaryComp( lhb, rhb, node.getType() );
        return "BLE ";
    }
    public java.lang.Object visit (Kitsch.yyTree.Gt node) {
        String rhb = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        String lhb = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        doBinaryComp( lhb, rhb, node.getType() );
        return "BGT ";
    }
    public java.lang.Object visit (Kitsch.yyTree.Gte node) {
        String rhb = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        String lhb = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        doBinaryComp( lhb, rhb, node.getType() );
        return "BGE ";
    }
    public java.lang.Object visit (Kitsch.yyTree.Eq node) {
        String rhb = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        String lhb = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        doBinaryComp( lhb, rhb, node.getType() );
        return "BEQ ";
    }
    public java.lang.Object visit (Kitsch.yyTree.Neq node) {
        String rhb = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        String lhb = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        doBinaryComp( lhb, rhb, node.getType() );
        return "BNE ";
    }
    /*
     * These actually write the correct logical operation out.
     */
    public java.lang.Object visit (Kitsch.yyTree.LgAnd node) {
        //this pushes the lhs and rhs of the child comp onto the stack and returns
        //the appropriate branching instruction to use
        String lhb = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        out.deferInstruction( negate( lhb ) );
        int lhpc = out.pc;
        String rhb = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        //write a branch if true one past the bra we're going to write
        out.writeInstruction( rhb + "#2" );
        //branch to the bra if first comparison failed
        out.finishInstruction( "#" + (out.pc - lhpc + 1) );
        //tell parent node to use bra
        return "BRA ";
    }
    public java.lang.Object visit (Kitsch.yyTree.LgOr node) {
        //this pushes the lhs and rhs of the child comp onto the stack and returns
        //the appropriate branching instruction to use
        String lhb = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        out.deferInstruction( lhb );
        int lhpc = out.pc;
        String rhb = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        //branch 1 past here if first comparison was true
        out.finishInstruction( "#" + (out.pc - lhpc + 2) );
        //tell parent node to use rhb to branch
        return rhb;
    }
    public java.lang.Object visit (Kitsch.yyTree.LgNot node) {
        //tell parent to use the opposite branch
        String ohb = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        return negate( ohb );
    }

    /**
     * Expr is just a container, pass the visit call onwards.
     */
     public java.lang.Object visit (Kitsch.yyTree.Expr node) {
        Kitsch.yyTree.Visit elem = (Kitsch.yyTree.Visit)node.get(0);
        return elem.visit( this );
     }

     /*
      * Since all the binary operations are pretty similar, this convience method does
      * all of them.  Get the LHS and RHS (null means that whatever the LHS/RHS
      * is at the top of the stack, otherwise it's something that needs to be loaded),
      * get them onto the stack in the right order, then perform the operation.
      * Something to note is that we want to load the RHS first, because it should be
      * one from the top of the stack.
      */
     private Object doBinaryOp( String lhs, String rhs, String op, int type ) {
         //detect if LHS's result is already on the stack; if it is, pull it off
         //and store it in a temporary variable.
         if ( lhs == null && rhs != null ) {
            SymbolTableEntry temp = currentScope.createTemp( type );
            out.writeInstruction( "STORE " + temp.memoryLoc );
            lhs = "" + temp.memoryLoc;
         }
         
         if ( rhs != null ) {
            //load RHS onto stack
            out.writeInstruction( "LOAD " + rhs );
         }
         if ( lhs != null ) {
            //load LHS onto stack
            out.writeInstruction( "LOAD " + lhs );
         }
         out.writeInstruction( op );
         return null; //result is on top of the stack
     }
     
     /* the rest of these simply call doBinaryOp with the right operation */
     public java.lang.Object visit (Kitsch.yyTree.Add node) {
         String rhs = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
         String lhs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
         String op;
         if ( node.getType() == OperatorRegistry.INT ) {
             op = "ADD";
             return doBinaryOp( lhs, rhs, op, node.getType() );
         } else if( node.getType() == OperatorRegistry.STRING){
             op = "STRADD";
             return doBinaryOp( lhs, rhs, op, node.getType() );
         } else if(node.getType() == OperatorRegistry.INT_ARRAY) {
             List<String> args = new ArrayList<String>();
             if(rhs != null) {
                 out.writeInstruction("LOAD "+rhs);
             }
             // Store the pointer to first array in a temp
             SymbolTableEntry b_pointer = currentScope.createTemp( OperatorRegistry.INT );
             out.writeInstruction("STORE "+b_pointer.memoryLoc);
             
             if(lhs != null) {
                 out.writeInstruction("LOAD "+lhs);
             }
             // Store the pointer to second array in a temp
             SymbolTableEntry a_pointer = currentScope.createTemp( OperatorRegistry.INT );
             out.writeInstruction("STORE "+a_pointer.memoryLoc);
             
             // Add the array arguments and call built-in function to add the arrays
             args.add(""+a_pointer.memoryLoc);
             args.add(""+b_pointer.memoryLoc);
             
             // This creates a new array with the concatenation of the elements
             callBuiltin("addIntArrays",args);
             
             // the builtin returns the pointer to the new array, 
             // so it is on the stack, just return null
             return null;
         } else if(node.getType() == OperatorRegistry.BOOL_ARRAY) {
             List<String> args = new ArrayList<String>();
             if(rhs != null) {
                 out.writeInstruction("LOAD "+rhs);
             }
             // Store the pointer to first array in a temp
             SymbolTableEntry b_pointer = currentScope.createTemp( OperatorRegistry.INT );
             out.writeInstruction("STORE "+b_pointer.memoryLoc);
             
             if(lhs != null) {
                 out.writeInstruction("LOAD "+lhs);
             }
             // Store the pointer to second array in a temp
             SymbolTableEntry a_pointer = currentScope.createTemp( OperatorRegistry.INT );
             out.writeInstruction("STORE "+a_pointer.memoryLoc);
             
             // Add the array arguments and call built-in function to add the arrays
             args.add(""+a_pointer.memoryLoc);
             args.add(""+b_pointer.memoryLoc);
             
             // This creates a new array with the concatenation of the elements
             callBuiltin("addBoolArrays",args);
             
             // the builtin returns the pointer to the new array, 
             // so it is on the stack, just return null
             return null;
         } else if(node.getType() == OperatorRegistry.STR_ARRAY) {
             List<String> args = new ArrayList<String>();
             if(rhs != null) {
                 out.writeInstruction("LOAD "+rhs);
             }
             // Store the pointer to first array in a temp
             SymbolTableEntry b_pointer = currentScope.createTemp( OperatorRegistry.INT );
             out.writeInstruction("STORE "+b_pointer.memoryLoc);
             
             if(lhs != null) {
                 out.writeInstruction("LOAD "+lhs);
             }
             // Store the pointer to second array in a temp
             SymbolTableEntry a_pointer = currentScope.createTemp( OperatorRegistry.INT );
             out.writeInstruction("STORE "+a_pointer.memoryLoc);
             
             // Add the array arguments and call built-in function to add the arrays
             args.add(""+a_pointer.memoryLoc);
             args.add(""+b_pointer.memoryLoc);
             
             // This creates a new array with the concatenation of the elements
             callBuiltin("addStringArrays",args);
             
             // the builtin returns the pointer to the new array, so it is on the stack, just return null
             return null;
         } else {
             System.err.println("Invalid type: "+node.getType()+" for '+' operator. ");
             System.exit(1);
         }
         return null;
     }
     public java.lang.Object visit (Kitsch.yyTree.Sub node) {
         String rhs = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
         String lhs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
         return doBinaryOp( lhs, rhs, "SUB", node.getType() );
     }
     public java.lang.Object visit (Kitsch.yyTree.Mul node) {
         String rhs = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
         String lhs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
         Integer lht = ((Kitsch.yyTree.Visit)node.get(0)).getType();
         Integer rht = ((Kitsch.yyTree.Visit)node.get(1)).getType();
         //string * int has to be done a little differently
         if ( lht == OperatorRegistry.STRING || rht == OperatorRegistry.STRING ) {
            //make sure lhs is the string
            if ( rht == OperatorRegistry.STRING ) {
                String temp = rhs;
                rhs = lhs;
                lhs = temp;
            }
            //make a temp variable for the string lhs
            if ( lhs == null ) {
                //on the stack, get it off and stick it in temp
                SymbolTableEntry temp = currentScope.createTemp( OperatorRegistry.STRING );
                lhs = "" + temp.memoryLoc;
                out.writeInstruction( "STORE " + lhs );
            } else {
                //variable or constant, we need it to be mutable
                SymbolTableEntry temp = currentScope.createTemp( OperatorRegistry.STRING );
                out.writeInstruction( "LOAD " + lhs );
                lhs = "" + temp.memoryLoc;
                out.writeInstruction( "STORE " + lhs );
            }
            //make a temp variable for the int rhs
            if ( rhs == null ) {
                //on the stack, get it off and stick it in temp
                SymbolTableEntry temp = currentScope.createTemp( OperatorRegistry.INT );
                rhs = "" + temp.memoryLoc;
                out.writeInstruction( "STORE " + rhs );
            } else {
                //variable or constant, we need it to be mutable
                SymbolTableEntry temp = currentScope.createTemp( OperatorRegistry.INT );
                out.writeInstruction( "LOAD " + rhs );
                rhs = "" + temp.memoryLoc;
                out.writeInstruction( "STORE " + rhs );
            }
            //push LHS (string) to the top of the stack
            out.writeInstruction( "LOAD " + lhs );
            //decrement RHS and do the comparison
            //get current PC - we'll need to branch back up here
            int topOfLoopPC = out.pc;
            out.writeInstruction( "LOAD #1" );
            out.writeInstruction( "LOAD " + rhs );
            out.writeInstruction( "SUB" );
            //store result in rhs and then load back onto stack
            out.writeInstruction( "STORE " + rhs );
            out.writeInstruction( "LOAD " + rhs );
            out.writeInstruction( "LOAD #0" );
            out.deferInstruction( "BEQ " ); //don't know how far to branch yet
            int bottomOfLoopPC = out.pc;
            //body of the loop - load LHS, multiply, branch up to the top
            out.writeInstruction( "LOAD " + lhs );
            out.writeInstruction( "STRADD" );
            out.writeInstruction( "BRA #" + (topOfLoopPC - out.pc) );
            out.finishInstruction( "#" + (out.pc - bottomOfLoopPC + 1) ); //finish the BEQ
            return null; //result on stack
         } else {
            return doBinaryOp( lhs, rhs, "MUL", node.getType() );
         }
     }
     public java.lang.Object visit (Kitsch.yyTree.Div node) {
         String rhs = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
         String lhs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
         return doBinaryOp( lhs, rhs, "DIV", node.getType() );
     }
     public java.lang.Object visit (Kitsch.yyTree.Mod node) {
         String rhs = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
         String lhs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
         return doBinaryOp( lhs, rhs, "MOD", node.getType() );
     }
     /*
      * There's no machine instruction for exp/pow, so this is implemented as a loop
      * of LOAD and MULs.
      * NOTE: DOES NOT HANDLE <0 OR FRACTIONAL EXPONENTS
      */
     public java.lang.Object visit (Kitsch.yyTree.Exp node) {
        //make a temp variable for the lhs
        String lhs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        if ( lhs == null ) {
            //on the stack, get it off and stick it in temp
            SymbolTableEntry temp = currentScope.createTemp( OperatorRegistry.INT );
            lhs = "" + temp.memoryLoc;
            out.writeInstruction( "STORE " + lhs );
        } else {
            //variable or constant, we need it to be mutable
            SymbolTableEntry temp = currentScope.createTemp( OperatorRegistry.INT );
            out.writeInstruction( "LOAD " + lhs );
            lhs = "" + temp.memoryLoc;
            out.writeInstruction( "STORE " + lhs );
        }
        String rhs = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
        //make a temp variable for the rhs
        if ( rhs == null ) {
            //on the stack, get it off and stick it in temp
            SymbolTableEntry temp = currentScope.createTemp( OperatorRegistry.INT );
            rhs = "" + temp.memoryLoc;
            out.writeInstruction( "STORE " + rhs );
        } else {
            //variable or constant, we need it to be mutable
            SymbolTableEntry temp = currentScope.createTemp( OperatorRegistry.INT );
            out.writeInstruction( "LOAD " + rhs );
            rhs = "" + temp.memoryLoc;
            out.writeInstruction( "STORE " + rhs );
         }
         //push LHS to the top of the stack
         out.writeInstruction( "LOAD " + lhs );
         //decrement RHS and do the comparison
         //get current PC - we'll need to branch back up here
         int topOfLoopPC = out.pc;
         out.writeInstruction( "LOAD #1" );
         out.writeInstruction( "LOAD " + rhs );
         out.writeInstruction( "SUB" );
         //store result in rhs and then load back onto stack
         out.writeInstruction( "STORE " + rhs );
         out.writeInstruction( "LOAD " + rhs );
         out.writeInstruction( "LOAD #0" );
         out.deferInstruction( "BEQ " ); //don't know how far to branch yet
         int bottomOfLoopPC = out.pc;
         //body of the loop - load LHS, multiply, branch up to the top
         out.writeInstruction( "LOAD " + lhs );
         out.writeInstruction( "MUL" );
         out.writeInstruction( "BRA #" + (topOfLoopPC - out.pc) );
         out.finishInstruction( "#" + (out.pc - bottomOfLoopPC + 1) ); //finish the BEQ
         return null; //result on stack
         
     }
     
     public java.lang.Object visit (Kitsch.yyTree.BwAnd node) {
         String rhs = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
         String lhs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
         return doBinaryOp( lhs, rhs, "ANDI", node.getType() );
     }
     
     public java.lang.Object visit (Kitsch.yyTree.BwOr node) {
         String rhs = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
         String lhs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
         return doBinaryOp( lhs, rhs, "ORI", node.getType() );
     }
     
     public java.lang.Object visit (Kitsch.yyTree.BwXor node) {
         String rhs = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
         String lhs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
         return doBinaryOp( lhs, rhs, "XORI", node.getType() );
     }
     
     public java.lang.Object visit (Kitsch.yyTree.BwNot node) {
         String ohs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
         if ( ohs != null ) {
            //load OHS onto stack
            out.writeInstruction( "LOAD " + ohs );
         }
         out.writeInstruction( "NOTI" );
         return null;
     }
    
     public java.lang.Object visit( Kitsch.yyTree.Call node ) {
         // lookup the function
         String funcname = (String)node.get(0);
         // func lookup
         FuncVisitor.Function f = lookupFunc(funcname);
         
         //if this is a call to len(), inline it
         if ( f.name.equals( "len" ) ) {
            //get the argument
            String arg = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
            if ( arg != null ) {
                //push arg on the stack
                out.writeInstruction( "LOAD " + arg );
            }
            //make a temp
            SymbolTableEntry pointer = currentScope.createTemp( OperatorRegistry.VOID_ARRAY );
            out.writeInstruction( "STORE " + pointer.memoryLoc );
            //load the length on the stack
            out.writeInstruction( "LOAD @" + pointer.memoryLoc );
            return null;
         }
         
         int num_arguments= 0;
         // Get # of arguments
         for(int i=1; i< node.size(); i++) {
             if(node.get(i) != null) {
                 num_arguments++;
             }
         }
         
         // Get # of parameters
         int min_parameters = 0;
         for(FuncVisitor.Parameter p: f.params) {
             if(p.defaultValue == null) {
                 min_parameters++;
             }
         }
         
         if(num_arguments < min_parameters) {
             System.err.println("ERROR: Function '"+funcname+"' requires at least "
                                +min_parameters+" parameters, "+ num_arguments+" given.");
             System.exit(1);
         }

         // Generate code to evaluate parameters
         Kitsch.yyTree.Visit elem;
         int totalParams = f.params.size();
         int numParams = totalParams-1;
         int num_default_params = totalParams - num_arguments;
         //out.writeInstruction("Total function parameters: "+totalParams);
         //out.writeInstruction("# function arguments supplied: "+num_arguments);
         //out.writeInstruction("# of default parameters: "+num_default_params);

         for ( int x = 1; x <= num_default_params ; x++ ) {
            FuncVisitor.Parameter p = f.params.get(totalParams - x);
            if ( p.type == OperatorRegistry.INT ) {
                out.writeInstruction( "LOAD #" + p.defaultValue );
            } else if ( p.type == OperatorRegistry.STRING ) {
                out.writeInstruction( "LOAD " + p.defaultValue ); 
            } else if ( p.type == OperatorRegistry.BOOL) {
                if ( ((Boolean)p.defaultValue).booleanValue() == true ) {
                    out.writeInstruction( "LOAD #1" );
                } else {
                    out.writeInstruction( "LOAD #0" );
                }
            } else {
                System.err.println("ERROR: Unknown type: "+p.type+" for parameter: "
                    +p.name+" to function: "+f.name);
            }
            numParams--;
         }
         
         for ( int x = node.size()-1; x > 0; x-- ) {
             elem = (Kitsch.yyTree.Visit)node.get(x);
             if ( elem != null ) {
                String param = (String)elem.visit(this);
                if ( elem.getType() == OperatorRegistry.BOOL ) {
                    handleBoolOp( param );
                } else if ( param != null ) {
                  out.writeInstruction( "LOAD " + param );
                }
                numParams--;
             }
         }
         
         // Load the stack pointer so we can save it later
         out.writeInstruction("LOADSP");
         
         // Make a new memory scope (stack frame), compute new stack pointer 
         //location, store it
         Scope s = new Scope(globalScope);
         out.writeInstruction("LOADSP");
         out.writeInstruction("LOAD #" + currentScope.memory.nextLoc() );
         out.writeInstruction("ADD" );
         out.writeInstruction("STORESP");
                  
         // push old stack pointer
         SymbolTableEntry sp = s.addToScope("0",OperatorRegistry.INT);
         out.writeInstruction("STORE "+sp.memoryLoc);

         // push return address
         SymbolTableEntry pc = s.addToScope("1",OperatorRegistry.INT);
         out.deferInstruction("LOAD ");
         out.writeInstruction("STORE "+pc.memoryLoc);   
             
         numParams = 0;
         for ( int x = 0; x < totalParams; x++ ) {
            //param is on the top of the stack, add it to the callee's scope
            FuncVisitor.Parameter p = f.params.get( numParams );
            SymbolTableEntry ap = s.addToScope( p.name, p.type );
            out.writeInstruction( "STORE " + ap.memoryLoc );
            numParams++;
         }

         // Jump to function
         out.finishInstruction("#"+(out.pc+1));
         if(f.startLoc != -1) {
             out.writeInstruction("JMP #"+f.startLoc); // startLoc might not be set yet!
         } else {
             out.writeInstruction( "JMP " + f.name );
             /*System.err.println("Function has not been compiled yet. " + 
                "Mutually recursive functions are not implemented yet!");*/
         }
         
         // Restore old stack pointer
         out.writeInstruction("LOAD "+sp.memoryLoc);
         out.writeInstruction("STORESP");
         
         //stick the return value in a temporary variable
         if ( node.getType() == OperatorRegistry.BOOL ) {
            //we should have the return (1 or 0) on the stack, but, since this is a bool,
            //we expect another arg and a branch
            out.writeInstruction( "LOAD #1" );
            return "BEQ ";
         } else {
            return null;
         }
     }
     
     // Array literal, leaves a pointer to the array on the stack
     public java.lang.Object visit( Kitsch.yyTree.ArrayLit node ) {
         int num_elements = 0;
         // Determine how many elements in the array
         for(int i=0; i<node.size(); i++) {
             if(node.get(i) != null) {
                 num_elements++;
             }
         }
         List<String> args = new ArrayList<String>();
         args.add("#"+(num_elements*2+2) );
         callBuiltin("malloc",args);
         
         // Malloc will leave the pointer to the array on the stack
         // We need to store that to some temporary location so we can 
         // reference it using indirect addressing
         SymbolTableEntry temp = currentScope.createTemp(OperatorRegistry.INT);
         out.writeInstruction("STORE "+temp.memoryLoc);
         
         // Load a copy of the heap address on the stack, we want to leave 
         // it there for assignment later on
         // Unfortunately we don't have a duplicate top of stack instruction
         out.writeInstruction("LOAD "+temp.memoryLoc);
         
         // Store the length of the array in the first position 
         out.writeInstruction("LOAD #"+num_elements);
         out.writeInstruction("STORE @"+temp.memoryLoc);
         
         // Store the allocated size of the array (not implemented yet, but it can be later)
         //   Calculate new address with offset
         out.writeInstruction("LOAD "+temp.memoryLoc);
         out.writeInstruction("LOAD #-1");
         out.writeInstruction("ADD");
         out.writeInstruction("STORE "+temp.memoryLoc);
         //   Now write the value to the new indirect address
         out.writeInstruction("LOAD #"+num_elements); 
         out.writeInstruction( "LOAD #2" );
         out.writeInstruction( "MUL" );         
         out.writeInstruction("STORE @"+temp.memoryLoc);
         
         // Store the array elements
         for(int i=0; i<node.size(); i++) {
            if(node.get(i) != null) {
                // Increment the temporary indirect address
                out.writeInstruction("LOAD "+temp.memoryLoc);
                out.writeInstruction("LOAD #-1");
                out.writeInstruction("ADD");
                out.writeInstruction("STORE "+temp.memoryLoc);
            
                // Compute/load the value for the array
                Kitsch.yyTree.Visit elem = (Kitsch.yyTree.Visit) node.get(i);
                String param = (String)elem.visit(this);
                if ( elem.getType() == OperatorRegistry.BOOL ) {
                    handleBoolOp( param );
                } else if ( param != null ) {
                  out.writeInstruction( "LOAD " + param );
                }
            
                // Now write the value to the new indirect address         
                out.writeInstruction("STORE @"+temp.memoryLoc);
            }
         }
         
         return null;
     }
     
     public java.lang.Object visit (Kitsch.yyTree.Parens node) {
         String inner = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
         //don't care if it's null, an address, or immediate, pass it on
         return inner;
     }
     
     public java.lang.Object visit( Kitsch.yyTree.Cast node ) {
        //compute the rhs
        Kitsch.yyTree.Visit elem = (Kitsch.yyTree.Visit)node.get(1);
        String rhs = (String)elem.visit( this );
        if ( elem.getType() == OperatorRegistry.BOOL ) {
            if(rhs != null) {
                handleBoolOp( rhs );
            }
        } else if ( rhs != null ) {
            //load rhs onto the stack
            out.writeInstruction( "LOAD " + rhs );
        }
        
        //do the cast
        String cast = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
        out.writeInstruction( cast );
        
        //casting to a bool requires some extra work
        String ret = null;
        if ( node.getType() == OperatorRegistry.BOOL ) {
            out.writeInstruction( "LOAD #1" );
            ret = "BEQ ";
        }
        
        return ret;
     }
     
     public java.lang.Object visit (Kitsch.yyTree.UNeg node) {
         String ohs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
         if ( ohs != null ) {
            out.writeInstruction( "LOAD " + ohs );
         }
         out.writeInstruction( "LOAD #0" );
         out.writeInstruction( "SUB" );
         return null;
     }
     
     public java.lang.Object visit (Kitsch.yyTree.UPos node) {
         String ohs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
         //don't care if it's null, an address, or immediate, pass it on
         return ohs;
     }
     
     public java.lang.Object visit (Kitsch.yyTree.NumCls node) {
         //return a string that represents the constant value of this node
         return "#" + node.get( 0 );
     }
     
     
    public java.lang.Object visit (Kitsch.yyTree.BoolCls node) {
        String bools = (String)node.get( 0 );
        out.writeInstruction( "LOAD #1" );
        if ( bools.equals( "True" ) ) {
            out.writeInstruction( "LOAD #1" );
        } else {
            out.writeInstruction( "LOAD #0" );
        }
        return "BEQ ";
    }
    
    public java.lang.Object visit (Kitsch.yyTree.StrCls node) {
        return node.get( 0 );
    }
    
    public java.lang.Object visit( Kitsch.yyTree.TypeCls node ) {
        String type = (String)node.get( 0 );
        if ( type.equals( "string" ) ) {
            return "TOSTR";
        } else if ( type.equals( "bool" ) ) {
            return "TOBOOL";
        } else if ( type.equals( "int" ) ) {
            return "TOINT";
        } else if( type.equals("int[]") ) {
            return "TOINT";
        } else if( type.equals("string[]") ) {
            return "TOINT";
        } else if( type.equals("bool[]") ) {
            return "TOINT";
        }
        return null;
    }
     
    public java.lang.Object visit (Kitsch.yyTree.IdCls node) {
        String ident = (String)node.get( 0 );
        SymbolTableEntry symbol = currentScope.get( ident );
        boolean absoluteMode = globalScope.get( ident ) != null;
        //if we got back null, this identifier does not exist
        if ( symbol == null ) {
            System.err.println( "Fatal: " + ident + " is not in scope." );
            System.exit( 1 );
         }
        //otherwise, return a string that represents the memory location of this ident
        if ( node.getType() == OperatorRegistry.BOOL && !assign_lhs ) {
            if ( absoluteMode ) {
                out.writeInstruction( "LOAD &" + symbol.memoryLoc );
            } else {
                out.writeInstruction( "LOAD " + symbol.memoryLoc );
            }
            out.writeInstruction( "LOAD #1" );
            return "BEQ ";
        } else {
            if ( absoluteMode ) {
                return "&" + symbol.memoryLoc;
            } else {
                return new Integer( symbol.memoryLoc ).toString();
            }
        }
     }
     
     public java.lang.Object visit( Kitsch.yyTree.EolCls node ) {
         return null;
     }
    
     public java.lang.Object visit ( Kitsch.yyTree.SubStr node) {
        int type = ((Kitsch.yyTree.Visit)node.get(0)).getType();
        if(type == OperatorRegistry.STRING) {
            //push both indices onto the stack
            String rhs = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
            if ( node.get(1) instanceof Kitsch.yyTree.Expr ) {
                //need to duplicate argument for the stack
                if ( rhs == null ) {
                    //on the stack already, pull it off
                    SymbolTableEntry temp = currentScope.createTemp( OperatorRegistry.INT );
                    out.writeInstruction( "STORE " + temp.memoryLoc );
                    out.writeInstruction( "LOAD " + temp.memoryLoc );
                    out.writeInstruction( "LOAD #1" );
                    out.writeInstruction( "ADD" );
                    out.writeInstruction( "LOAD " + temp.memoryLoc );
                } else {
                    //simply store on stack twice
                    out.writeInstruction( "LOAD " + rhs );
                    out.writeInstruction( "LOAD #1" );
                    out.writeInstruction( "ADD" );
                    out.writeInstruction( "LOAD " + rhs );
                }
            }
            String lhs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
            if ( lhs != null ) {
                //put on stack
                out.writeInstruction( "LOAD " + lhs );
            }
            out.writeInstruction( "SUBSTR" );
        } else {
            // It's an array access
            // Evaluate the RHS to get the memory offset
            String rhs = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
            if(rhs != null) {
                out.writeInstruction("LOAD "+rhs);
            } else {
                // Value of offset is on the stack already
            }
            
            // Get the LHS, should be a memory address if its an IdCls, 
            // or null if its an ArrayLit
            String lhs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
            if(lhs != null) {
                out.writeInstruction("LOAD "+lhs);
            } else {
                // Value of the pointer is on the stack already
            }
            out.writeInstruction("SUB");
            // Subtract another 2, to get past the length/allocation size that 
            // is in the array storage
            out.writeInstruction("LOAD #-2");
            out.writeInstruction("ADD");
            
            // Create a temporary location for computing effective address
            SymbolTableEntry temp = currentScope.createTemp(OperatorRegistry.INT);
            out.writeInstruction("STORE "+temp.memoryLoc);
            
            // Now that we've computed the effective address, access the element
            // via indirect addressing
            out.writeInstruction("LOAD @"+temp.memoryLoc);
            if ( node.getType() == OperatorRegistry.BOOL ) {
                out.writeInstruction( "LOAD #1" );
                return "BEQ ";
            }
        }
        return null;
    }
    
    public java.lang.Object visit( Kitsch.yyTree.Range node ) {
        if ( node.get( 1 ) == null ) {
            out.writeInstruction( "LOAD #0" );
        } else {
            String rhs = (String)((Kitsch.yyTree.Visit)node.get(1)).visit( this );
            if ( rhs != null ) {
                //load variable/constant onto the stack
                out.writeInstruction( "LOAD " + rhs );
            }
        }
        if ( node.get( 0 ) == null ) {
            out.writeInstruction( "LOAD #0" );
        } else {
            String lhs = (String)((Kitsch.yyTree.Visit)node.get(0)).visit( this );
            if ( lhs != null ) {
                //load variable/constant onto the stack
                out.writeInstruction( "LOAD " + lhs );
                return "BEQ ";
            }
        }
        return null;
    }
     
     /** hook for unknown classes. */
     public java.lang.Object visit (Kitsch.yyTree.Visit node) {
         System.err.println( "Unrecognized node:" + node );
         System.err.println( "Node class: " + node.getClass() );
         return null;
     }
}


